package p.ithorns.tools.excel.write;

import cn.idev.excel.EasyExcel;
import cn.idev.excel.ExcelWriter;
import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.write.builder.ExcelWriterSheetBuilder;
import cn.idev.excel.write.handler.SheetWriteHandler;
import cn.idev.excel.write.metadata.WriteSheet;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import p.ithorns.tools.excel.strategy.AdaptiveWidthStyleStrategy;
import p.ithorns.tools.excel.AssistTools;
import p.ithorns.tools.excel.convert.Converters;
import p.ithorns.tools.excel.exception.ExportException;
import p.ithorns.tools.excel.model.ImportSheetResult;
import p.ithorns.tools.excel.model.RowError;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * SingleSheetExporter
 * 单页导出
 *
 * @author HtL
 * @date 2024/7/25 8:59
 * @since 1.0.0
 */
public class SingleSheetExporter {

    private final static Logger log = LoggerFactory.getLogger(SingleSheetExporter.class);

    private SingleSheetExporter() {
    }

    /**
     * 设置响应
     *
     * @param response -
     * @param fileName -
     */
    public static void setResponse(HttpServletResponse response, String fileName) {
        // 设置响应信息
        // response.setContentType("application/vnd.ms-excel;charset=UTF-8");
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Cache-Control", "no-cache");
        String charset = StandardCharsets.UTF_8.name();
        response.setCharacterEncoding(charset);

        //设置导出文件名称（避免乱码）
        try {
            String name = URLEncoder.encode(fileName.concat(".xlsx"), charset);
            response.setHeader("Content-disposition", "attachment;filename=" + name);
        } catch (UnsupportedEncodingException e) {
            throw new ExportException(e.getMessage());
        }
    }

    /**
     * 导入模板
     *
     * @param fileName -文件名
     * @param clazz    -导入类型
     * @throws IOException -
     */
    public static <T> void template(HttpServletResponse response, String fileName, Class<T> clazz)
            throws IOException {
        // 默认文件名为表名
        SingleSheetExporter.doExport(response, fileName, fileName, clazz, Collections.emptyList());
    }

    /**
     * 导入模板
     *
     * @param fileName -文件名
     * @param clazz    -导入类型
     * @param handler  -导出处理器
     * @throws IOException -
     */
    public static <T> void template(HttpServletResponse response, String fileName, Class<T> clazz,
                                    SheetWriteHandler handler)
            throws IOException {
        // 设置响应信息
        setResponse(response, fileName);
        try (ServletOutputStream outputStream = response.getOutputStream()) {
            ExcelWriterSheetBuilder sheet = EasyExcel.write(outputStream)
                    .head(clazz)
                    .sheet(fileName);
            if (null != handler) {
                sheet.registerWriteHandler(handler);
            }
            sheet.doWrite(Collections.emptyList());
        }
    }

    /**
     * 指定类型导出到响应流
     *
     * @param fileName  -文件名
     * @param sheetName -表名
     * @param clazz     -导入类型
     * @throws IOException -
     */
    public static <T> void doExport(HttpServletResponse response,
                                    String fileName, String sheetName,
                                    Class<T> clazz, List<T> dataList) throws IOException {
        // 设置响应信息
        setResponse(response, fileName);
        List<List<String>> headNames = AssistTools.makeHeadNames(clazz);
        // sheet页默认为"0"
        sheetName = StringUtils.isEmpty(sheetName) ? "0" : sheetName;
        try (ServletOutputStream outputStream = response.getOutputStream()) {
            EasyExcel.write(outputStream)
                    .head(headNames)
                    .sheet(sheetName)
                    .registerConverter(Converters.DATE_CONVERTER)
                    .registerConverter(Converters.DATE_TIME_CONVERTER)
                    .registerWriteHandler(new AdaptiveWidthStyleStrategy())
                    .doWrite(dataList);
        }
    }


    /**
     * 指定类型导出到文件
     *
     * @param dest      -目标文件
     * @param clazz     -类型(头)
     * @param sheetName -表(页)名
     * @param dataList  -数据集
     * @param <T>       -具体类型
     */
    public static <T> void doExport(File dest, String sheetName, Class<T> clazz, List<T> dataList) {
        try (OutputStream os = Files.newOutputStream(dest.toPath())) {
            doExport(os, sheetName, clazz, dataList);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 指定头导入到文件
     *
     * @param dest      -目标文件
     * @param head      -头信息
     * @param sheetName -表(页)名
     * @param dataList  -数据集
     * @param <T>       -具体类型
     */
    public static <T> void doExport(File dest, String sheetName, List<List<String>> head, List<T> dataList) {
        try (OutputStream os = Files.newOutputStream(dest.toPath())) {
            doExport(os, sheetName, head, dataList);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 指定类型(头)导出到流
     *
     * @param os        -流
     * @param clazz     -类型
     * @param sheetName -表(页)名
     * @param dataList  -数据集
     * @param <T>       -具体类型
     */
    public static <T> void doExport(OutputStream os, String sheetName, Class<T> clazz, List<T> dataList) {
        // sheet页默认为"0"
        sheetName = StringUtils.isEmpty(sheetName) ? "0" : sheetName;
        EasyExcel.write(os).head(clazz).sheet(sheetName)
                .registerConverter(Converters.DATE_CONVERTER)
                .registerConverter(Converters.DATE_TIME_CONVERTER)
                .doWrite(dataList);

    }

    /**
     * 指定头导入到流
     *
     * @param os        -流
     * @param head      -头信息
     * @param sheetName -表(页)名
     * @param dataList  -数据集
     * @param <T>       -具体类型
     */
    public static <T> void doExport(OutputStream os, String sheetName, List<List<String>> head, List<T> dataList) {
        // sheet页默认为"0"
        sheetName = StringUtils.isEmpty(sheetName) ? "0" : sheetName;
        EasyExcel.write(os).head(head).sheet(sheetName)
                .registerConverter(Converters.DATE_CONVERTER)
                .registerConverter(Converters.DATE_TIME_CONVERTER)
                .doWrite(dataList);
    }


    /**
     * 根据ImportSheetResult创建错误文件，提供给业务消费
     *
     * @param importResult -导入结果
     * @param <T>          -数据类型
     * @throws IOException -
     */
    public static <T> MultipartFile createErrorMultipartFile(ImportSheetResult<T> importResult)
            throws IOException {
        Assert.notNull(importResult, "导入结果为null");

        List<RowError<T>> rowErrors = importResult.getRowErrors();
        if (CollectionUtils.isEmpty(rowErrors)) {
            log.info("没有异常导入数据");
            return null;
        }

        // 先将数据写到本地文件
        String sheetName = importResult.getSheetName();
        Path tempPath = Files.createTempFile(sheetName + "-ERR", ".xlsx");
        File tempFile = tempPath.toFile();

        // 获取T的类型
        Class<T> clazz = importResult.getType();

        // 写入Excel文件
        doExportErrorFile(tempFile, sheetName, clazz, rowErrors);

        // 转换成MultipartFile
        MultipartFile multipartFile = fileToMultipartFile(tempFile, sheetName);

        // 完了之后删除临时文件
        Files.delete(tempPath);

        return multipartFile;
    }

    /**
     * 导出错误数据到文件
     *
     * @param file      -错误文件
     * @param sheetName -表(页)名
     * @param clazz     -类型
     * @param dataList  -数据
     * @param <T>       -具体类型
     */
    public static <T> void doExportErrorFile(File file, String sheetName,
                                             Class<T> clazz, List<RowError<T>> dataList) {
        Map<String, Field> filedMap = getFiledMap(clazz);

        // head
        List<List<String>> headNames = getHeadNames(filedMap);

        // data
        List<List<Object>> rowValues = getRowValues(filedMap, dataList);

        sheetName = StringUtils.isEmpty(sheetName) ? "0" : sheetName;
        try (OutputStream os = Files.newOutputStream(file.toPath())) {
            EasyExcel.write(os)
                    .head(headNames)
                    .sheet(sheetName)
                    .registerConverter(Converters.DATE_CONVERTER)
                    .registerConverter(Converters.DATE_TIME_CONVERTER)
                    .doWrite(rowValues);
        } catch (Exception e) {
            throw new ExportException(e);
        }
    }

    private static List<List<String>> getHeadNames(final Map<String, Field> fieldMap) {
        List<List<String>> names = new ArrayList<>(32);
        Set<String> fieldNames = fieldMap.keySet();
        for (String name : fieldNames) {
            names.add(Collections.singletonList(name));
        }
        names.add(Collections.singletonList("错误信息"));
        return names;
    }

    private static <T> List<List<Object>> getRowValues(final Map<String, Field> fieldMap,
                                                       final List<RowError<T>> dataList) {
        List<List<Object>> values = new ArrayList<>(dataList.size());
        Collection<Field> fields = fieldMap.values();
        for (RowError<T> error : dataList) {
            T rowData = error.getRowData();
            if (null != rowData) {
                List<Object> row = new ArrayList<>(fields.size());
                for (Field field : fields) {
                    Object value = null;
                    try {
                        value = field.get(rowData);
                    } catch (IllegalAccessException e) {
                        log.error(e.getMessage());
                    }
                    row.add(value);
                }
                // 增加错误信息
                row.add(error.getMsg());
                values.add(row);
            }
        }
        return values;
    }


    public static Map<String, Field> getFiledMap(Class<?> clazz) {
        Field[] fields = clazz.getDeclaredFields();
        Map<String, Field> resultMap = new HashMap<>(fields.length);
        for (Field field : fields) {
            field.setAccessible(true);
            ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
            if (excelProperty != null) {
                String[] value = excelProperty.value();
                if (null != value && value.length > 0) {
                    resultMap.put(value[0], field);
                }
            }
        }
        return resultMap;
    }

    public static MultipartFile fileToMultipartFile(File file, String fieldName) {
        FileItemFactory factory = new DiskFileItemFactory(16, null);
        FileItem item = factory.createItem(fieldName,
                MediaType.MULTIPART_FORM_DATA_VALUE, true, file.getName());
        int bytesRead;
        byte[] buffer = new byte[8192];
        try (FileInputStream fis = new FileInputStream(file);
             OutputStream os = item.getOutputStream()) {
            while ((bytesRead = fis.read(buffer, 0, 8192)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new CommonsMultipartFile(item);
    }


    public static <T, E> void exportByPage(HttpServletResponse response,
                                           Class<T> clazz,
                                           Supplier<Integer> counter,
                                           Function<IPage<E>, List<T>> pageQuery) throws IOException {
        try (OutputStream os = response.getOutputStream()) {
            //指定用哪个class进行写出
            ExcelWriter build = EasyExcel.write(os, clazz).build();

            //设置一个sheet页存储所有导出数据
            WriteSheet writeSheet = EasyExcel.writerSheet("sheet").build();

            Integer total = counter.get(); // baseMapper.selectCount(null);
            long pageSize = 1000;
            long pages = total / pageSize;

            long startTime = System.currentTimeMillis();
            //大数据量时，进行分页查询写入
            for (int i = 0; i <= pages; i++) {
                Page<E> page = new Page<>();
                page.setCurrent(i + 1);
                page.setSize(pageSize);
                List<T> records = pageQuery.apply(page);
                build.write(records, writeSheet);
            }

            build.finish();
            log.info("导出耗时/ms:" + (System.currentTimeMillis() - startTime) + ",导出数据总条数：" + total);
        }
    }

}